package com.fishingwithme.domain.credit;

import com.fishingwithme.infrastructure.dtos.credit.CreditAvailableDto;
import com.fishingwithme.infrastructure.dtos.credit.CreditRecordDto;
import com.fishingwithme.infrastructure.dtos.credit.CreditSum;
import com.fishingwithme.infrastructure.dtos.credit.UserCreditDto;
import com.fishingwithme.infrastructure.mapper.CreditLevelRulesMapper;
import com.fishingwithme.infrastructure.mapper.CreditMapper;
import com.fishingwithme.infrastructure.mapper.CreditRuleMapper;
import com.fishingwithme.infrastructure.mapper.UserCreditMapper;
import com.fishingwithme.infrastructure.utils.CommonUtils;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;
import java.util.Objects;

@Service
public class CreditServiceImpl implements CreditService {
    private static final Long INIT_LOGIN = 1L;
    private static final Long DAILY_LOGIN_MARK = 2L;
    private static final Long SECRET_SPOT_VIEW = 4L;
    private static final Long SPOT_CREATE = 5L;
    private static final Long SHOW_HARVEST = 6L;
    @Autowired
    private CreditMapper creditMapper;
    @Autowired
    private CreditLevelRulesMapper levelRulesMapper;
    @Autowired
    private CreditRuleMapper ruleMapper;

    @Autowired
    private UserCreditMapper userCreditMapper;

    @Override
    public IPage<CreditRecord> list(Integer pageNum, Integer pageSize, Long userId) {
        Page<CreditRecord> page = new Page<>(pageNum, pageSize);
        return creditMapper.list(page, userId);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public void addUserRecord(Long userId) {
        CreditRule rule = ruleMapper.selectById(INIT_LOGIN);
        if (rule == null) {
            return;
        }
        CreditRecord record = new CreditRecord();
        record.setUserId(userId);
        record.setReason(rule.getBehaviorDesc());
        record.setBizId(INIT_LOGIN);
        record.setPoint(rule.getPoints());
        record.setCreatedBy(userId);
        record.setCreatedDate(CommonUtils.now());
        creditMapper.insert(record);

        CreditLevelRule levelRule = levelRulesMapper.selectByPoints(record.getPoint());
        UserCredit userCredit = new UserCredit();
        userCredit.setAccCredit(record.getPoint());
        userCredit.setCurrentCredit(record.getPoint());
        userCredit.setUserId(userId);
        userCredit.setCreditUpdateDate(CommonUtils
                .now());
        userCredit.setLvlUpdatedDate(CommonUtils.now());
        userCredit.setLvl(levelRule.getLevelId());
        userCredit.setLvlName(levelRule.getLevelName());
        userCreditMapper.insert(userCredit);
    }

    private void updateUserCredit(Long userId) {

        UserCredit credit = userCreditMapper.selectById(userId);
        CreditSum creditSum = creditMapper.selectCreditSum(userId);
        CreditLevelRule levelRule = levelRulesMapper.selectByPoints(creditSum.getAccTotal());

        Date now = CommonUtils.now();
        if (!Objects.equals(levelRule.getLevelId(), credit.getLvl())) {
            credit.setLvl(levelRule.getLevelId());
            credit.setLvlName(levelRule.getLevelName());
            credit.setLvlUpdatedDate(now);
        }
        credit.setAccCredit(creditSum.getAccTotal());
        credit.setCurrentCredit(creditSum.getCurrentCredit());
        credit.setCreditUpdateDate(now);
        userCreditMapper.updateById(credit);

    }

    @Override
    public UserCreditDto get(Long userId) {

        UserCredit credit = userCreditMapper.selectById(userId);
        CreditLevelRule levelRule = levelRulesMapper.selectById(credit.getLvl());
        CreditLevelRule nextRule = levelRulesMapper.selectById(credit.getLvl() + 1);

        List<CreditRecord> records = this.list(1, 4, userId).getRecords();
        double va = BigDecimal.valueOf(credit.getAccCredit()).divide(BigDecimal.valueOf(nextRule != null ? nextRule.getMinPoints() : Integer.MAX_VALUE), 2,
                RoundingMode.HALF_UP).doubleValue();
        return UserCreditDto.builder()
                .autoSkipAds(credit.getEnableAutoSkipAds())
                .currentLevel(levelRule.getLevelId())
                .currentLevelName(levelRule.getLevelName())
                .currentLvlDesc(levelRule.getCoreTrait())
                .currentLevelPoints(credit.getCurrentCredit())
                .nextLevel(nextRule != null ? nextRule.getLevelId() : Integer.MAX_VALUE)
                .nextLevelName(nextRule != null ? nextRule.getLevelName() : "最高级")
                .nextLevelPoints(nextRule != null ? nextRule.getMinPoints() : Integer.MAX_VALUE)
                .progressPercentage(va)
                .totalPoints(credit.getAccCredit())
                .remainingPoints((nextRule != null ? nextRule.getMinPoints() : Integer.MAX_VALUE) - credit.getAccCredit())
                .recentRecords(records.stream().map(CreditRecordDto::toDto).toList())
                .build();
    }

    @Override
    public Boolean skipAds(Boolean skipAds, Long userId) {
        UpdateWrapper<UserCredit> updateWrapper = new UpdateWrapper<>();

        updateWrapper.eq("user_id", userId);

        updateWrapper.set("enable_auto_skip_ads", skipAds);

        updateWrapper.set("credit_update_date", CommonUtils.now());

        int cnt = userCreditMapper.update(updateWrapper);
        return cnt > 0 ? Boolean.TRUE
                : Boolean.FALSE;
    }

    @Override
    public List<CreditLevelRule> getCreditLvls(Long userId) {
        return levelRulesMapper.selectAll();
    }

    @Override
    public IPage<CreditRule> getCreditRules(Integer pageNum, Integer pageSize, Long userId) {
        Page<CreditRule> page = new Page<>(pageNum, pageSize);
        Wrapper<CreditRule> wrapper = Wrappers.lambdaQuery(CreditRule
                        .class)
                .orderByAsc((SFunction<CreditRule, Object>) CreditRule::getLevelRangeStart)
                .orderByDesc((SFunction<CreditRule, Object>) CreditRule::getSortOrder);

        return ruleMapper.selectPage(page, wrapper);
    }

    @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
    @Override
    public void dailyMark(Long userId) {
        Boolean marked = creditMapper.hasMarked(DAILY_LOGIN_MARK, userId, CommonUtils.today());
        if (marked) {
            return;
        }
        this.commonReward(userId, DAILY_LOGIN_MARK);
    }

    @Override
    public Integer transferPrivateSpotCredit(Long fromUserId, Long toUserId, Integer transferVal) {
        CreditRule rule = ruleMapper.selectById(SECRET_SPOT_VIEW);
        if (rule == null) {
            return 0;
        }
        transferVal = transferVal == null ? rule.getPoints() : transferVal;

        CreditSum creditSum = creditMapper.selectCreditSum(fromUserId);
        if (creditSum.getCurrentCredit() < Math.abs(transferVal)) {
            throw new RuntimeException("用户积分不够.");
        }

        Date now = CommonUtils.now();
        CreditRecord descRecord = new CreditRecord();
        descRecord.setUserId(fromUserId);
        descRecord.setReason(rule.getBehaviorDesc());
        descRecord.setBizId(rule.getId());

        descRecord.setPoint(-Math.abs(transferVal));
        descRecord.setCreatedBy(fromUserId);
        descRecord.setCreatedDate(now);
        creditMapper.insert(descRecord);

        CreditRecord ascRecord = new CreditRecord();
        ascRecord.setUserId(toUserId);
        ascRecord.setReason(rule.getBehaviorDesc());
        ascRecord.setBizId(rule.getId());
        ascRecord.setPoint(Math.abs(transferVal));
        ascRecord.setCreatedBy(toUserId);
        ascRecord.setCreatedDate(now);
        creditMapper.insert(ascRecord);

        this.updateUserCredit(fromUserId);
        this.updateUserCredit(toUserId);
        return transferVal;
    }

    @Override
    public void rewardSpotCreateCredit(Long userId) {
        this.commonReward(userId, SPOT_CREATE);
    }

    @Override
    public void rewardHarvestShowCredit(Long userId) {
        this.commonReward(userId, SHOW_HARVEST);
    }

    @Override
    public CreditAvailableDto isCreditAvailableForCollect(Long spotId) {
        CreditAvailableDto available = creditMapper.getSpotCreditAvailable(spotId,CommonUtils.getUserId(),SECRET_SPOT_VIEW);
        available.evaluateAvailable();
        return available;
    }

    @Override
    public Boolean setCredit(Long spotId, Integer credit) {
        int cnt = creditMapper.setCredit(spotId,credit,CommonUtils.getUserId());
        return cnt > 0;
    }

    private void commonReward(Long userId, Long ruleId) {
        CreditRule rule = ruleMapper.selectById(ruleId);
        if (rule == null) {
            return;
        }
        CreditRecord record = new CreditRecord();
        record.setUserId(userId);
        record.setReason(rule.getBehaviorDesc());
        record.setBizId(rule.getId());
        record.setPoint(rule.getPoints());
        record.setCreatedBy(userId);
        record.setCreatedDate(CommonUtils.now());
        creditMapper.insert(record);

        this.updateUserCredit(userId);
    }
}
